package num

import (
	"math/big"
	"showen7/num"
	"testing"
)

func TestIntegerType(t *testing.T) {
	x := num.Integer(5)
	if x.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", x.Type())
	}
}

func TestIntegerString(t *testing.T) {
	x := num.Integer(42)
	if x.String() != "42" {
		t.Errorf("Expected \"42\", got \"%s\"", x.String())
	}
}

func TestIntegerADD(t *testing.T) {
	x := num.Integer(5)
	y := num.Integer(3)
	result := x.ADD(y)

	if result.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", result.Type())
	}

	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type, got %T", result)
	}

	if intResult != 8 {
		t.Errorf("Expected 8, got %d", intResult)
	}
}

func TestIntegerSUB(t *testing.T) {
	x := num.Integer(5)
	y := num.Integer(3)
	result := x.SUB(y)

	if result.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", result.Type())
	}

	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type, got %T", result)
	}

	if intResult != 2 {
		t.Errorf("Expected 2, got %d", intResult)
	}
}

func TestIntegerMUL(t *testing.T) {
	x := num.Integer(5)
	y := num.Integer(3)
	result := x.MUL(y)

	if result.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", result.Type())
	}

	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type, got %T", result)
	}

	if intResult != 15 {
		t.Errorf("Expected 15, got %d", intResult)
	}
}

func TestIntegerDIV(t *testing.T) {
	// Exact division
	x := num.Integer(6)
	y := num.Integer(3)
	result := x.DIV(y)

	if result.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", result.Type())
	}

	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type, got %T", result)
	}

	if intResult != 2 {
		t.Errorf("Expected 2, got %d", intResult)
	}

	// Division with remainder should produce Rational
	x = num.Integer(5)
	y = num.Integer(2)
	result = x.DIV(y)

	if result.Type() != num.RATIONAL_TYPE {
		t.Errorf("Expected num.RATIONAL_TYPE, got %v", result.Type())
	}

	ratResult, ok := result.(*num.Rational)
	if !ok {
		t.Errorf("Expected Rational type, got %T", result)
	}

	expected := big.NewRat(5, 2)
	actual := (*big.Rat)(ratResult)
	if actual.Cmp(expected) != 0 {
		t.Errorf("Expected 5/2, got %s", actual.String())
	}
}

func TestIntegerNEG(t *testing.T) {
	x := num.Integer(42)
	result := x.NEG()

	if result.Type() != num.INTEGER_TYPE {
		t.Errorf("Expected num.INTEGER_TYPE, got %v", result.Type())
	}

	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type, got %T", result)
	}

	if intResult != -42 {
		t.Errorf("Expected -42, got %d", intResult)
	}

	// Test negation of minimum int64 value (should overflow to BigInteger)
	x = num.Integer(-1 << 63)
	result = x.NEG()

	if result.Type() != num.BIGINTEGER_TYPE {
		t.Errorf("Expected num.BIGINTEGER_TYPE, got %v", result.Type())
	}
}

func TestIntegerOverflowToBigInteger(t *testing.T) {
	// Test addition overflow with maximum values
	x := num.Integer(1<<62 - 1)
	y := num.Integer(1 << 62)
	result := x.ADD(y)

	// This should overflow and return a BigInteger
	// Actually, this won't overflow since 2^62-1 + 2^62 = 2^63-1 which is still within int64 range
	// Let's test with actual overflow values
	x = num.Integer(1 << 62)
	y = num.Integer(1 << 62)
	result = x.ADD(y)

	// 2^62 + 2^62 = 2^63 which overflows int64
	if result.Type() != num.BIGINTEGER_TYPE {
		t.Errorf("Expected num.BIGINTEGER_TYPE on overflow, got %v", result.Type())
	}

	// Test multiplication overflow
	x = num.Integer(1 << 31)
	y = num.Integer(1 << 32)
	result = x.MUL(y)

	if result.Type() != num.BIGINTEGER_TYPE {
		t.Errorf("Expected num.BIGINTEGER_TYPE on overflow, got %v", result.Type())
	}
}

func TestBigIntegerType(t *testing.T) {
	x := (*num.BigInteger)(big.NewInt(42))
	if x.Type() != num.BIGINTEGER_TYPE {
		t.Errorf("Expected num.BIGINTEGER_TYPE, got %v", x.Type())
	}
}

func TestBigIntegerString(t *testing.T) {
	x := (*num.BigInteger)(big.NewInt(42))
	if x.String() != "42" {
		t.Errorf("Expected \"42\", got \"%s\"", x.String())
	}
}

func TestBigIntegerADD(t *testing.T) {
	x := (*num.BigInteger)(big.NewInt(5))
	y := (*num.BigInteger)(big.NewInt(3))
	result := x.ADD(y)

	// Result should be converted to Integer since it fits
	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type when result fits, got %T", result)
	}

	if intResult != 8 {
		t.Errorf("Expected 8, got %d", intResult)
	}

	// Test conversion to Integer when result fits
	x = (*num.BigInteger)(big.NewInt(100))
	y = (*num.BigInteger)(big.NewInt(-50))
	result = x.ADD(y)

	intResult, ok = result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type when result fits, got %T", result)
	}

	if intResult != 50 {
		t.Errorf("Expected 50, got %d", intResult)
	}
}

func TestBigIntegerOperationsWithOtherTypes(t *testing.T) {
	x := (*num.BigInteger)(big.NewInt(10))

	// Add with Integer
	y := num.Integer(5)
	result := x.ADD(y)

	// Result should be converted to Integer since it fits
	intResult, ok := result.(num.Integer)
	if !ok {
		t.Errorf("Expected Integer type when result fits, got %T", result)
	}

	if intResult != 15 {
		t.Errorf("Expected 15, got %d", intResult)
	}
}

func TestRationalType(t *testing.T) {
	x := (*num.Rational)(big.NewRat(1, 2))
	if x.Type() != num.RATIONAL_TYPE {
		t.Errorf("Expected num.RATIONAL_TYPE, got %v", x.Type())
	}
}

func TestRationalString(t *testing.T) {
	x := (*num.Rational)(big.NewRat(1, 2))
	if x.String() != "1/2" {
		t.Errorf("Expected \"1/2\", got \"%s\"", x.String())
	}
}

func TestRationalOperations(t *testing.T) {
	x := (*num.Rational)(big.NewRat(1, 2))
	y := (*num.Rational)(big.NewRat(1, 3))

	// Addition
	result := x.ADD(y)
	ratResult, ok := result.(*num.Rational)
	if !ok {
		t.Errorf("Expected Rational type, got %T", result)
	}

	expected := big.NewRat(5, 6)
	actual := (*big.Rat)(ratResult)
	if actual.Cmp(expected) != 0 {
		t.Errorf("Expected 5/6, got %s", actual.String())
	}

	// Subtraction
	result = x.SUB(y)
	ratResult, ok = result.(*num.Rational)
	if !ok {
		t.Errorf("Expected Rational type, got %T", result)
	}

	expected = big.NewRat(1, 6)
	actual = (*big.Rat)(ratResult)
	if actual.Cmp(expected) != 0 {
		t.Errorf("Expected 1/6, got %s", actual.String())
	}

	// Multiplication
	result = x.MUL(y)
	ratResult, ok = result.(*num.Rational)
	if !ok {
		t.Errorf("Expected Rational type, got %T", result)
	}

	expected = big.NewRat(1, 6)
	actual = (*big.Rat)(ratResult)
	if actual.Cmp(expected) != 0 {
		t.Errorf("Expected 1/6, got %s", actual.String())
	}

	// Division
	result = x.DIV(y)
	ratResult, ok = result.(*num.Rational)
	if !ok {
		t.Errorf("Expected Rational type, got %T", result)
	}

	expected = big.NewRat(3, 2)
	actual = (*big.Rat)(ratResult)
	if actual.Cmp(expected) != 0 {
		t.Errorf("Expected 3/2, got %s", actual.String())
	}
}

func TestRealType(t *testing.T) {
	x := num.Real(3.14)
	if x.Type() != num.REAL_TYPE {
		t.Errorf("Expected num.REAL_TYPE, got %v", x.Type())
	}
}

func TestRealString(t *testing.T) {
	x := num.Real(3.14)
	str := x.String()
	if str == "" {
		t.Errorf("Expected non-empty string, got empty string")
	}
}

func TestRealOperations(t *testing.T) {
	x := num.Real(3.5)
	y := num.Real(2.0)

	// Addition
	result := x.ADD(y)
	realResult, ok := result.(num.Real)
	if !ok {
		t.Errorf("Expected Real type, got %T", result)
	}

	if float64(realResult) != 5.5 {
		t.Errorf("Expected 5.5, got %f", realResult)
	}

	// Subtraction
	result = x.SUB(y)
	realResult, ok = result.(num.Real)
	if !ok {
		t.Errorf("Expected Real type, got %T", result)
	}

	if float64(realResult) != 1.5 {
		t.Errorf("Expected 1.5, got %f", realResult)
	}

	// Multiplication
	result = x.MUL(y)
	realResult, ok = result.(num.Real)
	if !ok {
		t.Errorf("Expected Real type, got %T", result)
	}

	if float64(realResult) != 7.0 {
		t.Errorf("Expected 7.0, got %f", realResult)
	}

	// Division
	result = x.DIV(y)
	realResult, ok = result.(num.Real)
	if !ok {
		t.Errorf("Expected Real type, got %T", result)
	}

	if float64(realResult) != 1.75 {
		t.Errorf("Expected 1.75, got %f", realResult)
	}
}

func TestComplexType(t *testing.T) {
	x := num.Complex(1 + 2i)
	if x.Type() != num.COMPLEX_TYPE {
		t.Errorf("Expected num.COMPLEX_TYPE, got %v", x.Type())
	}
}

func TestComplexString(t *testing.T) {
	x := num.Complex(1 + 2i)
	str := x.String()
	if str == "" {
		t.Errorf("Expected non-empty string, got empty string")
	}
}

func TestComplexOperations(t *testing.T) {
	x := num.Complex(1 + 2i)
	y := num.Complex(2 + 3i)

	// Addition
	result := x.ADD(y)
	compResult, ok := result.(num.Complex)
	if !ok {
		t.Errorf("Expected Complex type, got %T", result)
	}

	if complex128(compResult) != 3+5i {
		t.Errorf("Expected 3+5i, got %v", complex128(compResult))
	}

	// Subtraction
	result = x.SUB(y)
	compResult, ok = result.(num.Complex)
	if !ok {
		t.Errorf("Expected Complex type, got %T", result)
	}

	if complex128(compResult) != -1-1i {
		t.Errorf("Expected -1-1i, got %v", complex128(compResult))
	}

	// Multiplication
	result = x.MUL(y)
	compResult, ok = result.(num.Complex)
	if !ok {
		t.Errorf("Expected Complex type, got %T", result)
	}

	if complex128(compResult) != -4+7i {
		t.Errorf("Expected -4+7i, got %v", complex128(compResult))
	}

	// Division
	result = x.DIV(y)
	compResult, ok = result.(num.Complex)
	if !ok {
		t.Errorf("Expected Complex type, got %T", result)
	}

	// Just check that we got a complex result, not exact value matching due to floating point precision
	if compResult.Type() != num.COMPLEX_TYPE {
		t.Errorf("Expected num.COMPLEX_TYPE, got %v", compResult.Type())
	}
}

func TestTypePromotion(t *testing.T) {
	// Integer with Rational -> Rational
	x := num.Integer(2)
	y := (*num.Rational)(big.NewRat(1, 2))
	result := x.ADD(y)

	if result.Type() != num.RATIONAL_TYPE {
		t.Errorf("Expected num.RATIONAL_TYPE, got %v", result.Type())
	}

	// BigInteger with Real -> Real
	bigX := (*num.BigInteger)(big.NewInt(10))
	realY := num.Real(3.14)
	result = bigX.MUL(realY)

	if result.Type() != num.REAL_TYPE {
		t.Errorf("Expected num.REAL_TYPE, got %v", result.Type())
	}

	// Real with Complex -> Complex
	realX := num.Real(1.5)
	compY := num.Complex(1 + 2i)
	result = realX.ADD(compY)

	if result.Type() != num.COMPLEX_TYPE {
		t.Errorf("Expected num.COMPLEX_TYPE, got %v", result.Type())
	}
}
